using System;
using System.Collections.Generic;
using System.Text;
using System.CodeDom;
using System.CodeDom.Compiler;

using Destra.Tools.DACGenerator.Config;

namespace Destra.Tools.DACGenerator.CodeGenerators
{
	public class ReaderCodeGenerator : CodeDomGenerator
	{
		public ReaderCodeGenerator(CodeDomProvider codeDomProvider)	:
			base(codeDomProvider)
		{
			
		}

		private CodeTypeDeclaration GetClass(string className, List<QueryInfo.OutputColumn> outputColumns)
		{
			CodeTypeDeclaration codeTypeDeclaration = new CodeTypeDeclaration(className);
			codeTypeDeclaration.Attributes = MemberAttributes.Public;
			codeTypeDeclaration.IsClass = true;
			codeTypeDeclaration.IsPartial = true;
			codeTypeDeclaration.BaseTypes.Add(
				new CodeTypeReference(
					typeof(Destra.Common.DataAccess.ReaderBase)
					)
				);

			// constructor
			CodeConstructor codeConstructor = new CodeConstructor();
			codeConstructor.Attributes = MemberAttributes.Public;
			codeConstructor.Parameters.Add(
				new CodeParameterDeclarationExpression(
					new CodeTypeReference(
						typeof(System.Data.SqlClient.SqlDataReader)
						),
					"reader"
					)
				);
			codeConstructor.BaseConstructorArgs.Add(
				new CodeVariableReferenceExpression(
					"reader"
					)
				);
			codeTypeDeclaration.Members.Add(codeConstructor);

			// column accessors
			foreach (QueryInfo.OutputColumn outputColumn in outputColumns)
			{
				CodeMemberProperty codeMemberProperty = new CodeMemberProperty();
				codeMemberProperty.Attributes = MemberAttributes.Public | MemberAttributes.Final;
				codeMemberProperty.Name = outputColumn.Name;
				
				if ((outputColumn.Type.IsValueType == true) &&
					(outputColumn.Nullable == true))
				{
					codeMemberProperty.Type = new CodeTypeReference(
							"System.Nullable",
							new CodeTypeReference[]
									{
									new CodeTypeReference(
										outputColumn.Type
										)
									}
							);
				}
				else
				{
					codeMemberProperty.Type = new CodeTypeReference(
							outputColumn.Type
							);
				}

				if (outputColumn.Nullable == true)
				{
					codeMemberProperty.GetStatements.Add(
						new CodeConditionStatement(
							new CodeBinaryOperatorExpression(
								new CodeMethodInvokeExpression(
									new CodeArrayIndexerExpression(
										new CodeThisReferenceExpression(),
										new CodeExpression[]
													{
													new CodePrimitiveExpression(outputColumn.Name)
													}
										),
									"GetType",
									new CodeExpression[0]
									),
								CodeBinaryOperatorType.IdentityEquality,
								new CodeTypeOfExpression(
									typeof(System.DBNull)
									)
								),
							new CodeStatement[]
									{
									outputColumn.Type.IsValueType ?
										new CodeMethodReturnStatement(
											new CodeObjectCreateExpression(
												new CodeTypeReference(
													"System.Nullable",
													new CodeTypeReference[]
														{
														new CodeTypeReference(
															outputColumn.Type
															)
														}
													),
												new CodeExpression[0]
												)
											) :
										new CodeMethodReturnStatement(
											new CodePrimitiveExpression(null)
											)
									},
							new CodeStatement[]
									{
									new CodeMethodReturnStatement(
										new CodeCastExpression(
											new CodeTypeReference(
												outputColumn.Type
												),
											new CodeArrayIndexerExpression(
												new CodeThisReferenceExpression(),
												new CodeExpression[]
														{
														new CodePrimitiveExpression(outputColumn.Name)
														}
												)
											)
										)
									}
							)
						);
				}
				else
				{
					codeMemberProperty.GetStatements.Add(
						new CodeMethodReturnStatement(
							new CodeCastExpression(
								new CodeTypeReference(
									outputColumn.Type
									),
								new CodeArrayIndexerExpression(
									new CodeThisReferenceExpression(),
									new CodeExpression[]
												{
												new CodePrimitiveExpression(outputColumn.Name)
												}
									)
								)
							)
						);
				}

				codeTypeDeclaration.Members.Add(codeMemberProperty);
			}

			return codeTypeDeclaration;
		}

		public string Generate(DacGeneratorConfig dacGeneratorConfig, List<QueryInfo.OutputColumn> outputColumns)
		{
			CodeNamespace codeNamespace = new CodeNamespace(dacGeneratorConfig.ClassConfig.Namespace);

			codeNamespace.Types.Add(GetClass(dacGeneratorConfig.ClassConfig.Class + "Reader", outputColumns));

			return base.GenerateCodeFromNamespace(codeNamespace);
		}
	}
}
